/* eslint-disable no-console */
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
import * as fs from 'fs';

function snakeToCamel(m: string) {
  const uncapped = m
    .replace(/_([a-z])/g, g => g[1].toUpperCase())
    .replace(/json/g, 'JSON')
    .replace(/sql/g, 'SQL');
  return uncapped[0].toUpperCase() + uncapped.slice(1);
}

type MalloyInterfaceFieldType = {
  type: string;
  optional: boolean;
  array: boolean;
};

type MalloyInterfaceType =
  | {
      name: string;
      type: 'struct';
      fields: Record<string, MalloyInterfaceFieldType>;
    }
  | {name: string; type: 'union'; options: Record<string, string>}
  | {name: string; type: 'enum'; values: Record<string, number>};

const types: Record<string, MalloyInterfaceType> = {};

for (const file of fs.readdirSync('./generated-types')) {
  if (file === 'index.ts') continue;
  const text = fs.readFileSync(`./generated-types/${file}`, 'utf8');
  const lines = text.split('\n');
  const firstTypeLine = lines.findIndex(line => line.startsWith('export'));
  const firstNonTypeLine = lines.findIndex(
    line => line.endsWith('Args;') || line.endsWith('Args {')
  );
  const actualTypes = lines.slice(firstTypeLine, firstNonTypeLine).join('\n');
  const isAnEnum = actualTypes.match(
    /export enum ([A-Za-z]+) {\n( {4}([A-Z_]+) = \d+,?\n)+}/
  );
  if (isAnEnum) {
    const name = isAnEnum[1];
    const values: Record<string, number> = {};
    const matches = actualTypes.matchAll(/ {4}([A-Z_]+) = (\d+)/g);
    for (const match of matches) {
      const [name, valueString] = [match[1].toLowerCase(), match[2]];
      values[name] = parseInt(valueString);
    }
    types[name] = {
      type: 'enum',
      name,
      values,
    };
    continue;
  }
  const isUnion = actualTypes.match(
    /export enum ([A-Za-z]+)Type {\n(?: {4}[A-Za-z]+With[A-Za-z_]+ = "([a-z_]+)",?\n)+}/
  );
  if (isUnion) {
    const name = isUnion[1];
    const options: Record<string, string> = {};
    const optionMatches = actualTypes.matchAll(
      / {4}([a-z_]+): ([A-Za-z]+).I[A-Za-z]+;/g
    );
    for (const optionMatch of optionMatches) {
      const [name, type] = [optionMatch[1], optionMatch[2]];
      options[name] = type;
    }
    types[name] = {
      type: 'union',
      name,
      options,
    };
    continue;
  }
  // ... for "clarity"
  const isStruct = true;
  if (isStruct) {
    const nameMatch = actualTypes.match(/export interface I([A-Za-z]+) {/)!;
    const name = nameMatch[1];
    const fields: Record<string, MalloyInterfaceFieldType> = {};
    const fieldMatches = actualTypes.matchAll(
      / {4}([a-z_]+)(\?)?: (?:([a-z]+)|([A-Za-z]+)\.[A-Za-z]+|I([A-Za-z]+)|(?:Array<([a-z]+)>)|(?:Array<([A-Za-z]+)\.[A-Za-z]+>)|(?:Array<I([A-Za-z]+)>));/g
    );
    for (const fieldMatch of fieldMatches) {
      const [
        name,
        optionalQ,
        basicType,
        specialType,
        recursiveType,
        arrayBasicType,
        arraySpecialType,
        arrayRecursiveType,
      ] = [
        fieldMatch[1],
        fieldMatch[2],
        fieldMatch[3],
        fieldMatch[4],
        fieldMatch[5],
        fieldMatch[6],
        fieldMatch[7],
        fieldMatch[8],
      ];
      const optional = optionalQ === '?';
      const type =
        (basicType && {type: basicType, optional, array: false}) ??
        (specialType && {type: specialType, optional, array: false}) ??
        (recursiveType && {type: recursiveType, optional, array: false}) ??
        (arrayBasicType && {
          type: arrayBasicType,
          array: true,
          optional,
        }) ??
        (arraySpecialType && {
          type: arraySpecialType,
          array: true,
          optional,
        }) ??
        (arrayRecursiveType && {
          type: arrayRecursiveType,
          array: true,
          optional,
        });
      if (!type) throw new Error(`Invalid type ${fieldMatch[0]}`);
      fields[name] = type;
    }
    types[name] = {
      type: 'struct',
      name,
      fields,
    };
  }
}

console.log(`/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
/*
 * Autogenerated.
 * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
 */

type MalloyInterfaceFieldType = {
  type: string;
  optional: boolean;
  array: boolean;
};

type MalloyInterfaceType =
  | {
      name: string;
      type: 'struct';
      fields: Record<string, MalloyInterfaceFieldType>;
    }
  | {name: string; type: 'union'; options: Record<string, string>}
  | {name: string; type: 'enum'; values: Record<string, number>};
`);

console.log(
  `export const MALLOY_INTERFACE_TYPES: Record<string, MalloyInterfaceType> = ${JSON.stringify(
    types,
    null,
    2
  )
    .replace(/"/g, "'")
    .replace(/'\n/g, "',\n")
    .replace(/true\n/g, 'true,\n')
    .replace(/false\n/g, 'false,\n')
    .replace(/}\n/g, '},\n')}\n`
);

for (const typeName in types) {
  const type = types[typeName];
  if (type.type === 'enum') {
    console.log(
      `export type ${type.name} = ${Object.keys(type.values)
        .map(v => `'${v}'`)
        .join(' | ')};\n`
    );
  } else if (type.type === 'struct') {
    console.log(`export type ${type.name} = {
${Object.entries(type.fields)
  .map(
    ([name, field]) =>
      `  ${name}${
        field.optional ? '?' : ''
      }: ${malloyInterfaceFieldTypeToString(field)};`
  )
  .join('\n')}
}\n`);
  } else if (type.type === 'union') {
    console.log(
      `export type ${type.name}Type = ${Object.keys(type.options)
        .map(kind => `'${kind}'`)
        .join(' | ')};\n`
    );
    console.log(
      `export type ${type.name} = ${Object.keys(type.options)
        .map(kind => `${type.name}With${snakeToCamel(kind)}`)
        .join(' | ')};\n`
    );
    for (const kind in type.options) {
      const childType = type.options[kind];
      console.log(
        `export type ${type.name}With${snakeToCamel(
          kind
        )} = {kind: '${kind}'} & ${childType};\n`
      );
    }
  }
}

function malloyInterfaceFieldTypeToString(type: MalloyInterfaceFieldType) {
  return type.array ? `Array<${type.type}>` : type.type;
}
